
import re
import sys
import time
import codecs
import threading

import lookup
import correct
from templates import *;

import xmpppy as xmpp


class DictBot(threading.Thread):

	COMMAND_PREFIX = '/'
	POLL_INTERVAL = 0.1
	SUBSCRIBE_INTERVAL = 0.3

	admins = []

	def __init__(self, domain, username, password, url, port):
		threading.Thread.__init__(self) 
		self.domain = domain
		self.username = username
		self.password = password
		self.url = url
		self.port = port
		self.JID = '%s@%s' % (username, domain)
		self.hitCount = 0
		self.msgCount = 0
		self.hitRate = 0.0
		self.msgRate = 0.0
		self.exist = True
		self.autoStatus = False


	def log(self, message):
		print message
		f = codecs.open('%s.log' % (self.username), 'a', encoding='utf-8')
		f.write('%s\n' % message)
		f.close()


	def run(self):
		self.client = xmpp.Client(self.domain, debug=[])
		self.client.connect(server = (self.url, self.port))
		self.client.auth(self.username, self.password)
		self.client.sendInitPresence()
		self.roster = self.client.getRoster()
		self.userCount = len(self.roster.getItems())
		self.client.RegisterHandler('message', self.handle_Message)
		self.client.RegisterHandler('presence', self.handle_Presence)
		self.log('XMPP %s: connected to %s:%d' % (self.JID, self.url, self.port))
		while self.exist:
			self.client.Process()
			time.sleep(DictBot.POLL_INTERVAL)
			# In order to avoid rejection from server, sleep for a while after each poll
			# While this thread sleeps, another thread can run
		del self.client


	def handle_Presence(self, dispatcher, presence):
		"Accepts user subscriptions or forward it to the bot with least contacts"
		self.hitCount += 1
		senderJID = (presence.getFrom()).getStripped()

		type = presence.getType()
		if type == 'subscribe':
			self.log('ROSTER %s: request from %s' % (self.JID, senderJID))
			self.roster.Authorize(senderJID)
			self.log('ROSTER %s: authorized to %s' % (self.JID, senderJID))
			time.sleep(self.SUBSCRIBE_INTERVAL)

		elif type == 'subscribed':
			# After successful subscription, welcome
			dispatcher.send(xmpp.Message(senderJID, REPLY_PREFIX + WELCOME_MSG))
			self.log('ROSTER %s: subscribed by %s' % (self.JID, senderJID))
			self.userCount += 1
			
			# Update status message
			if self.autoStatus:
				presence = xmpp.protocol.Presence()
				presence.setStatus(AUTO_STATUS_MSG % (self.userCount))
				dispatcher.send(presence)


	def handle_Message(self, dispatcher, message):
		"Processes commands and dictionary lookup requests"
		self.hitCount += 1
		request = message.getBody()
		if request and not request.startswith(REPLY_PREFIX):
			self.msgCount += 1
			senderJID = (message.getFrom()).getStripped()
			try: # If the request is non-ascii, converting to ascii string will raise exception
				str(request)
			except StandardError:
				dispatcher.send(xmpp.Message(senderJID, REPLY_PREFIX + INPUT_WRONG_MSG))
				return
			self.log('\t%s << %s : %s' % (self.JID, senderJID, request))
			request = request.strip()
			if request.startswith(DictBot.COMMAND_PREFIX) and len(request) > 1:
				self.process_Command(dispatcher, senderJID, request[1:])
			else:
				self.process_Query(dispatcher, senderJID, request)


	def process_Command(self, dispatcher, senderJID, command):
		command = command.split()
		cmd = command[0]

# USER COMMANDS SECTION
		if cmd == 'help':
			reply = USER_HELP_MSG

		elif cmd == 'about':
			reply = ABOUT_MSG

		elif cmd == 'stats':    # Display system statistics
			reply = STATS_MSG % (self.userCount, lookup.mainDictSize, self.msgRate, self.hitRate)

		elif cmd == 'invite':
			if len(command) == 2:
				subscribee = command[1]
				if not self.is_ValidJID(subscribee):
					reply = INVALID_EMAIL_MSG % subscribee
				else:
					self.cursor.execute("""
SELECT host FROM roster_now WHERE subscriber='%s' AND subscription NOT IN ('none', 'remove')
""" % (subscribee))
					hosts = self.cursor.fetchall()
					if hosts:	# If subscription already exists
						reply = ALREADY_USING_MSG % (subscribee, ', '.join(hosts))
					else:
						if self.JID == self.nextHost:
							self.roster.Subscribe(subscribee)
						else:
							# Send the subscribe request to the least populated bot
							dispatcher.send(xmpp.Message(self.nextHost, '/subscribe %s' % (subscribee)))
						reply =  INVITED_MSG % (subscribee)
			else:
				reply = INVITE_USAGE_MSG

		elif cmd == 'comment':
			if len(command) > 1:
				comment = (' '.join(command[1:]))[:256]
				f = open('%s.cmt' % self.username, 'a+t')
				f.write('%s >> %s\n' % (senderJID, comment))
				f.close()
				reply = COMMENT_REPLY_MSG
			else:
				reply = COMMENT_USAGE_MSG

		# If the sender is not in the friend list, don't check any further for admin commands
		elif senderJID not in DictBot.admins and senderJID not in DictBot.siblings:
			reply = USER_HELP_MSG

# INTER-BOT MESSAGES SECTION

		elif cmd == 'subscribe':
			if len(command) == 2:
				if self.is_ValidJID(command[1]):
					self.log('ROSTER %s: subscribe to %s forced by %s' % (self.JID, command[1], senderJID))
					self.roster.Subscribe(command[1])
			return
	
# ADMIN COMMANDS SECTION
		elif cmd == 'admin':    
			reply = ADMIN_HELP_MSG

		elif cmd == 'status':
			if len(command) > 1:
				self.autoStatus = False
				presence = xmpp.protocol.Presence()
				presence.setStatus(' '.join(command[1:]))
				dispatcher.send(presence)
				return
			reply = STATUS_USAGE_MSG

		elif cmd == 'autostatus':
			self.autoStatus = not self.autoStatus
			presence = xmpp.protocol.Presence()
			if self.autoStatus:
				presence.setStatus(AUTO_STATUS_MSG % (self.userCount))
			else:
				presence.setStatus('')
			dispatcher.send(presence)
			return


		elif cmd == 'exit':
			self.exist = False
			return

		elif cmd == 'shutdown': # Shutdown the bot completely
			sys.exit(0)

		else:
			reply = ADMIN_HELP_MSG
		reply = REPLY_PREFIX + reply
		dispatcher.send(xmpp.Message(senderJID, reply))
		self.log('\t%s >> %s : %s' % (self.JID, senderJID, reply))
		

	def process_Query(self, dispatcher, senderJID, query):
		query = query.lower()
		if len(query.split()) > 1:
			reply = REPLY_PREFIX + INPUT_TOOLONG_MSG
		else:
			meaning = self.get_Meaning(query)
			if meaning is None:   # if word is not in dictionary, get stem word
				correction = lookup.wordnet.morphy(str(query))	# Get stem word
				meaning = self.get_Meaning(correction)
				if meaning is None:
					correction = correct.correct(query)		# Bayesian Correction
					meaning = self.get_Meaning(correction)
					if meaning is None:
						reply = REPLY_PREFIX + WORD_UNKNOWN_MSG
					else:
						reply = REPLY_PREFIX + '(correction) ' + AUTO_CORRECT_MSG % (correction, meaning)
				else:
					reply = REPLY_PREFIX + '(morphology) ' + AUTO_CORRECT_MSG % (correction, meaning)
			else:
				reply = REPLY_PREFIX + '\n' + meaning
		dispatcher.send(xmpp.Message(senderJID, reply))
		self.log('\t%s >> %s : %s' % (self.JID, senderJID, reply[:reply.find('\n')]))


	def get_Meaning(self, query):
		if lookup.mainDict.has_key(query):
			return lookup.mainDict[query]
		return None
	

	def is_ValidJID(self, JID):
		return re.match('\S+@\S+\.\w+', JID)
